﻿using System;
using System.Collections.Generic;
using System.Text;
using System.Xml;
using System.Linq;
using System.Threading;
using System.Reflection;
using System.Timers;

namespace Akronus.TaskScheduler
{
    /// <summary>
    /// Manages concrete task.
    /// </summary>
    public class TaskManager
    {
        #region Fields

        private ITaskTimer timer = null;

        private ITaskInfoDataService dataService = null;

        private ITaskRunner taskRunner = null;

        private ITaskScheduleStrategy taskRunStrategy = null;

        private ITaskLog taskLog = null;

        private object locker = new object();

        private bool isRunning = false;

        #endregion

        #region Properties


        /// <summary>
        /// Gets or sets the task imlpementation.
        /// </summary>
        /// <value>The task.</value>
        public ITask Task { get; set; }

        /// <summary>
        /// Gets or sets the type of the task.
        /// </summary>
        /// <value>The type of the task.</value>
        public string TaskType { get; set; }

        public bool AllowRunningTaskWithSameType { get; set; }

        /// <summary>
        /// Gets or sets a value indicating whether the task is enabled.
        /// </summary>
        /// <value><c>true</c> if enabled; otherwise, <c>false</c>.</value>
        public bool Enabled { get; set; }

        /// <summary>
        /// Gets or sets the configuration node.
        /// </summary>
        /// <value>The configuration node.</value>
        public XmlNode ConfigurationNode { get; set; }

        #endregion

        #region Public Constructors

        /// <summary>
        /// Initializes a new instance of the <see cref="TaskManager"/> class.
        /// </summary>
        /// <param name="dataService">The data service.</param>
        /// <param name="timer">The timer.</param>
        /// <param name="taskRunStrategy">The task run strategy.</param>
        /// <param name="threadTaskRunner">The thread task runner.</param>
        public TaskManager(ITaskInfoDataService dataService, ITaskTimer timer, ITaskScheduleStrategy taskRunStrategy, ITaskRunner threadTaskRunner, ITaskLog taskLog)
        {
            this.dataService = dataService;
            this.taskRunner = threadTaskRunner;
            this.taskRunStrategy = taskRunStrategy;
            this.taskLog = taskLog;

            this.timer = timer;
            this.timer.Elapsed += new ElapsedEventHandler(timer_Elapsed);
        }

        #endregion

        #region Public Methods

        /// <summary>
        /// Starts the task.
        /// </summary>
        public void Start()
        {
            if (Enabled)
            {
                var taskSchedule = ConfigureInterval();
                if (taskSchedule.RunTaskImmediately)
                    this.StartTask();

                this.timer.Start();
            }
        }

        /// <summary>
        /// Stops the task.
        /// </summary>
        public void Stop()
        {
            this.timer.Stop();
        }

        /// <summary>
        /// Runs the task immediately in spite of schedule.
        /// </summary>
        /// <returns><c>true</c> if task was started; otherwise, <c>false</c>. Task is not started when it is in progress.</returns>
        public bool Run()
        {
            return this.StartTask();
        }

        public void Abort()
        {
            this.taskRunner.Abort();
        }

        #endregion

        #region Private Methods

        private TaskSchedule ConfigureInterval()
        {
            var result = taskRunStrategy.GetTaskSchedule();
            timer.Interval = result.Interval;
            return result;
        }

        private bool StartTask()
        {
            lock (locker)
            {
                if (!isRunning)
                {
                    IList<TaskInfo> tasks = null;
                    if (!AllowRunningTaskWithSameType)
                    {
                        tasks = dataService.GetTaskInfos(this.TaskType);
                    }
                    if (tasks == null || !tasks.Any(t => t.Status == TaskInfoTypes.Started))
                    {
                        isRunning = true;
                        SaveTaskInfo(TaskInfoTypes.Started);
                        taskRunner.Execute(ExecuteTask);
                        return true;
                    }
                    else
                    {
                        taskLog.Add("Unable to start the task because another task with the same type is running at the moment.");
                    }
                }
                else
                {
                    taskLog.Add("Unable to start task because the previous run has not finished.");
                }
                return false;
            }
        }

        private void ExecuteTask()
        {
            string status = String.Empty;
            try
            {
                taskLog.Add(string.Format("Task '{0}' has started.", this.Task.Name), LogPriority.Medium);
                Task.Status = TaskInfoTypes.Started;
                Task.Execute();
                if (Task.Status == TaskInfoTypes.Started)
                {
                    Task.Status = TaskInfoTypes.Finished;
                    status = TaskInfoTypes.Finished;
                }
                else
                {
                    status = TaskInfoTypes.Failed;
                }
                SaveTaskInfo(status);
                taskLog.Add(string.Format("Task '{0}' has finished.", this.Task.Name), LogPriority.Medium);
            }
            catch (Exception e)
            {
                taskLog.Add(string.Format("Task '{0}' has failed. The following exception was thrown: {1} ", this.Task.Name, e.ToString()), LogPriority.High);
                status = TaskInfoTypes.Failed;
                Task.Status = TaskInfoTypes.Failed;
                SaveTaskInfo(status);
            }
            finally
            {
                if (Enabled)
                    ConfigureInterval();
                isRunning = false;
            }
        }

        private void timer_Elapsed(object sender, ElapsedEventArgs e)
        {
            StartTask();
        }

        private void SaveTaskInfo(string status)
        {
            var item = new TaskInfo()
            {
                Name = this.Task.Name,
                LastRunTime = DateTime.Now,
                Status = status,
                Type = this.TaskType
            };
            dataService.SaveTaskInfo(item);
        }

        #endregion
    }
}
